#include "QueryValidator.h"

#include "PKB.h"

#include <stdlib.h>
#include <iostream>
#include <algorithm>

QueryValidator::QueryValidator()
{
	std::vector<EntityType> if_;
	if_.push_back( kIF );

	std::vector<EntityType> while_;
	while_.push_back( kWHILE );

	std::vector<EntityType> assign;
	assign.push_back( kASSIGN );

	std::vector<EntityType> variable;
	variable.push_back( kVARIABLE );

	std::vector<EntityType> procedure;
	procedure.push_back( kPROCEDURE );

	std::vector<EntityType> statements;
	statements.push_back( kIF ); statements.push_back( kCALL ); statements.push_back( kSTMT ); statements.push_back( kWHILE ); statements.push_back( kASSIGN ); statements.push_back( kPROG_LINE );
			
	RelationPrototype modifiesP;
	modifiesP.parameters.push_back( procedure );
	modifiesP.parameters.push_back( variable );
	_table[ MODIFIES_P ] = modifiesP;

	RelationPrototype modifiesS;
	modifiesS.parameters.push_back( statements );
	modifiesS.parameters.push_back( variable );
	_table[ MODIFIES_S ] = modifiesS;

	RelationPrototype usesP;
	usesP.parameters.push_back( procedure );
	usesP.parameters.push_back( variable );
	_table[ USES_P ] = usesP;

	RelationPrototype usesS;
	usesS.parameters.push_back( statements );
	usesS.parameters.push_back( variable );
	_table[ USES_S ] = usesS;

	RelationPrototype calls;
	calls.parameters.push_back( procedure );
	calls.parameters.push_back( procedure );
	_table[ CALLS ] = calls;

	RelationPrototype callsT;
	callsT.parameters.push_back( procedure );
	callsT.parameters.push_back( procedure );
	_table[ CALLS_T ] = callsT;

	RelationPrototype parent;
	parent.parameters.push_back( statements );
	parent.parameters.push_back( statements );
	_table[ PARENT ] = parent;

	RelationPrototype parentT;
	parentT.parameters.push_back( statements );
	parentT.parameters.push_back( statements );
	_table[ PARENT_T ] = parentT;

	RelationPrototype follows;	
	follows.parameters.push_back( statements );
	follows.parameters.push_back( statements );
	_table[ FOLLOWS ] = follows;

	RelationPrototype followsT;
	followsT.parameters.push_back( statements );
	followsT.parameters.push_back( statements );
	_table[ FOLLOWS_T ] = followsT;
	// START OF PROG_LINE TO STMT
	RelationPrototype next;	
	next.parameters.push_back( statements );
	next.parameters.push_back( statements );
	_table[ NEXT ] = next;

	RelationPrototype nextT;
	nextT.parameters.push_back( statements );
	nextT.parameters.push_back( statements );
	_table[ NEXT_T ] = nextT;

	RelationPrototype nextbip;	
	nextbip.parameters.push_back( statements );
	nextbip.parameters.push_back( statements );
	_table[ NEXTBIP ] = nextbip;

	RelationPrototype nextbipT;
	nextbipT.parameters.push_back( statements );
	nextbipT.parameters.push_back( statements );
	_table[ NEXTBIP_T ] = nextbipT;

	RelationPrototype affects;
	affects.parameters.push_back( assign );
	affects.parameters.push_back( assign );
	_table[ AFFECTS ] = affects;

	RelationPrototype affectsT;
	affectsT.parameters.push_back( assign );
	affectsT.parameters.push_back( assign );
	_table[ AFFECTS_T ] = affectsT;

	RelationPrototype affectsbip;	
	affectsbip.parameters.push_back( assign );
	affectsbip.parameters.push_back( assign );
	_table[ AFFECTSBIP ] = affectsbip;

	RelationPrototype affectsbipT;
	affectsbipT.parameters.push_back( assign );
	affectsbipT.parameters.push_back( assign );
	_table[ AFFECTSBIP_T ] = affectsbipT;
	// END
	RelationPrototype patternI;
	patternI.parameters.push_back( if_ );
	patternI.parameters.push_back( variable );
	_table[ PATTERN_I ] = patternI;

	RelationPrototype patternW;
	patternW.parameters.push_back( while_ );
	patternW.parameters.push_back( variable );
	_table[ PATTERN_W ] = patternW;

	RelationPrototype patternA;
	patternA.parameters.push_back( assign );
	patternA.parameters.push_back( variable );
	_table[ PATTERN_A ] = patternA;

	RelationPrototype sibling;
	sibling.parameters.push_back( statements );
	sibling.parameters.push_back( statements );
	_table[ SIBLING ] = sibling;

}

QueryValidator::~QueryValidator()
{
}

bool QueryValidator::ValidateParameter( query_parameter& parameter )
{
	switch ( parameter.type )
	{		
		case LINE:
		{
			// check if between 1 and # lines
			int line_number = atoi( parameter.value.data() );
			if ( line_number > PKB::shortcutTable.GetStatementCount() )
			{
				this->_errors.push_back( "Line number exceeded number of statements." );
				return false;
			}
			break;
		}

		case ENTITY:
			// entity not defined
			if ( this->_entities.find( parameter.value ) == this->_entities.end() )
			{
				this->_errors.push_back( "Entity " + parameter.value + " is not defined." );
				return false;
			}
			break;

		case ATTRIBUTE:
		{
			size_t period = parameter.value.find( '.' );
			std::string entity = parameter.value.substr( 0, period );
			std::string attribute = parameter.value.substr( period + 1 );

			// entity not defined
			if ( this->_entities.find( entity ) == this->_entities.end() )
			{
				this->_errors.push_back( "Entity " + entity + " is not defined." );
				return false;
			}

			EntityType type =  this->_entities[ entity ];

			// attribute name invalid for entity type
			if ( ( attribute.compare( "procName" ) == 0 ) && ( type != kPROCEDURE )
				|| ( attribute.compare( "varName" ) == 0 ) && ( type != kVARIABLE )
				|| ( attribute.compare( "value" ) == 0 ) && ( type != kCONSTANT )
				|| ( attribute.compare( "stmt#" ) == 0 ) && ( type != kSTMT ) && ( type != kASSIGN ) && ( type != kIF ) && ( type != kWHILE ) )
			{
				this->_errors.push_back( "Entity " + entity + " has no attribute " + attribute + "." );
				return false;
			}
			break;
		}

		default:
			break;
	}
	return true;
}

bool QueryValidator::ResolveOverload( query_relation& relation )
{
	if ( relation.type == MODIFIES )
	{
		if ( this->_entities.find( relation.parameters[ 0 ].value ) == this->_entities.end() )
			return false;

		EntityType type = this->_entities[ relation.parameters[ 0 ].value ];

		switch ( type )
		{
			case kPROCEDURE:
				relation.type = MODIFIES_P;
				break;

			case kIF:
			case kCALL:
			case kSTMT:
			case kWHILE:
			case kASSIGN:
			case kPROG_LINE:
				relation.type = MODIFIES_S;
				break;

			default:
				this->_errors.push_back( "Modifies accepts only stmt/procedure." );
				return false;
		}
	}
	else if ( relation.type == USES )
	{
		if ( this->_entities.find( relation.parameters[ 0 ].value ) == this->_entities.end() )
			return false;

		EntityType type = this->_entities[ relation.parameters[ 0 ].value ];

		switch ( type )
		{
			case kPROCEDURE:
				relation.type = USES_P;
				break;

			case kIF:
			case kCALL:
			case kSTMT:
			case kWHILE:
			case kASSIGN:
			case kPROG_LINE:
				relation.type = USES_S;
				break;

			default:
				this->_errors.push_back( "Uses accepts only stmt/procedure." );
				return false;
		}
	}
	else if ( relation.type == PATTERN )
	{
		if ( this->_entities.find( relation.parameters[ 0 ].value ) == this->_entities.end() )
			return false;

		EntityType type = this->_entities[ relation.parameters[ 0 ].value ];

		switch ( type )
		{
			case kWHILE:
				relation.type = PATTERN_W;
				break;

			case kASSIGN:
				relation.type = PATTERN_A;
				break;

			default:
				this->_errors.push_back( "Pattern accepts only assign/if/while." );
				return false;
		}
	}
	return true;
}

bool QueryValidator::ValidatePrototype( query_relation& relation )
{
	if ( relation.type == WITH )
	{		
		ParameterType lhs_type = relation.parameters[0].type;
		ParameterType rhs_type = relation.parameters[1].type;
		if ( lhs_type == ATTRIBUTE )
		{
			size_t period = relation.parameters[0].value.find( '.' );
			std::string attribute = relation.parameters[0].value.substr( period + 1 );
			if ( ( attribute.compare( "procName" ) == 0 ) || ( attribute.compare( "varName" ) == 0 ) )
				lhs_type = NAME;
			else if ( ( attribute.compare( "value" ) == 0 ) || ( attribute.compare( "stmt#" ) == 0 ) )
				lhs_type = LINE;
		}
		if ( rhs_type == ATTRIBUTE )
		{
			size_t period = relation.parameters[1].value.find( '.' );
			std::string attribute = relation.parameters[1].value.substr( period + 1 );
			if ( ( attribute.compare( "procName" ) == 0 ) || ( attribute.compare( "varName" ) == 0 ) )
				rhs_type = NAME;
			else if ( ( attribute.compare( "value" ) == 0 ) || ( attribute.compare( "stmt#" ) == 0 ) )
				rhs_type = LINE;
		}

		if ( lhs_type != rhs_type )
		{
			this->_errors.push_back( "Invalid comparison: " + relation.parameters[0].value + " = " + relation.parameters[1].value + "." );
				return false;
		}
		return true;
	}

	RelationPrototype prototype = this->_table[ relation.type ];

	std::vector<bool> valid( prototype.parameters.size(), false );

	for ( int i = 0; i < prototype.parameters.size(); i++ )
	{
		if ( relation.parameters[ i ].type != ENTITY )
		{
			valid[ i ] = true;
			continue;
		}

		for ( int j = 0; j < prototype.parameters[ i ].size(); j++ )
		{
			if ( this->_entities.find( relation.parameters[ i ].value ) == this->_entities.end() )
			{
				continue;
			}

			if ( this->_entities[ relation.parameters[ i ].value ] == prototype.parameters[ i ][ j ] )
			{
				valid[ i ] = true;
			}
		}
	}

	for ( int i = 0; i < valid.size(); i++ )
	{
		if ( !valid[ i ] )
		{
			this->_errors.push_back( "Invalid parameters." );
			return false;
		}
	}

	return true;
}

bool QueryValidator::Validate( query_tree& tree )
{
	// reset internal variables
	this->_errors.clear();
	this->_entities.clear();

	bool valid = true;

	for ( auto variable = tree.variables.begin(); variable != tree.variables.end(); variable++ )
	{
		// redefinition of variable name
		if ( this->_entities.find( variable->name ) != this->_entities.end() )
		{
			this->_errors.push_back( "Entity " + variable->name + " redefinition." );
			valid = false;
		}
		this->_entities[ variable->name ] = variable->type;
	}

	for ( auto target = tree.targets.begin(); target != tree.targets.end(); target++ )
	{
		if ( !this->ValidateParameter( *target ) )
			valid = false;
	}

	for ( auto relation = tree.relations.begin(); relation != tree.relations.end(); relation++ )
	{
		if ( !this->ResolveOverload( *relation ) )
			valid = false;
		
		if ( !this->ValidatePrototype( *relation ) )
			valid = false;
		
		for ( auto parameter = relation->parameters.begin(); parameter != relation->parameters.end(); parameter++ )
		{
			if ( !this->ValidateParameter( *parameter ) )
				valid = false;
		}
	}

	return valid;
}

std::list<std::string> QueryValidator::GetErrors()
{
	return this->_errors;
}